﻿using System;

using Microsoft.Xna.Framework;


namespace SpaceAdventureLib.Universe
{
    [Serializable]
    public abstract class Entity
    {

        #region "Properties"

        private Space space = null;
        public Space Space
        {
            get { return this.space; }
            set { this.space = value; }
        }

        private Vector3 location = Vector3.Zero;
        public Vector3 Location
        {
            get { return this.location; }
        }
        
        private Vector3 velocity = Vector3.Zero;
        public Vector3 Velocity
        {
            get { return this.velocity; }
            set { this.velocity = value; }
        }

        private Vector3 acceleration = Vector3.Zero;
        public Vector3 Acceleration
        {
            get { return this.acceleration; }
            set { this.acceleration = value; }
        }

        private Vector3 rotation = Vector3.Zero;
        public Vector3 Rotation
        {
            get { return this.rotation; }
        }

        private bool wrapRotation = true;
        public bool WrapRotation
        {
            get { return this.wrapRotation; }
            set { this.wrapRotation = value; }
        }

        private Vector3 minRotation = Vector3.Zero;
        public Vector3 MinRotation
        {
            get { return this.minRotation; }
            set { this.minRotation = value; }
        }

        private Vector3 maxRotation = new Vector3(MathHelper.TwoPi);
        public Vector3 MaxRotation
        {
            get { return this.maxRotation; }
            set { this.maxRotation = value; }
        }

        private Vector3 rotVelocity = Vector3.Zero;
        public Vector3 RotationVelocity
        {
            get { return this.rotVelocity; }
            set { this.rotVelocity = value; }
        }

        private Vector3 rotAccel = Vector3.Zero;
        public Vector3 RotationAcceleration
        {
            get { return this.rotAccel; }
            set { this.rotAccel = value; }
        }

        private Vector3 forward = Vector3.Forward;
        public Vector3 Forward
        {
            get { return this.forward; }
            protected set { this.forward = value; }
        }

        private Vector3 up = Vector3.Up;
        public Vector3 Up
        {
            get { return this.up; }
            protected set { this.up = value; }
        }
        
        private Vector3 right = Vector3.Right;
        public Vector3 Right
        {
            get { return this.right; }
            protected set { this.right = value; }
        }

        private Matrix world = Matrix.Identity;
        /// <summary>
        /// The world transform matrix.
        /// </summary>
        public Matrix World
        {
            get { return this.world; }
            protected set { this.world = value; }
        }

        private float mass = 0.0f;
        /// <summary>
        /// Mass of the entity. Heavier objects require stiffer springs with less
        /// damping to move at the same rate as lighter objects.
        /// </summary>
        public float Mass
        {
            get { return this.mass; }
            set { this.mass = value; }
        }

        
        #endregion

        #region "Variables"

        private bool updateDirections = true;

        #endregion

        #region "Abstract Methods"

        public abstract void FinaliseInitialization();

        public abstract void Update(GameTime gameTime);

        #endregion

        #region "Methods"
        
        protected virtual void PreInitialize(Space space)
        {
            this.space = space;
        }

        protected void UpdateDirections()
        {
            if (this.updateDirections)
            {

                // Create rotation matrix from rotation amount
                Matrix rotationMatrix = Matrix.CreateFromAxisAngle(this.right, this.rotation.Y) * Matrix.CreateRotationY(this.rotation.X);

                // Rotate orientation vectors
                this.forward = Vector3.TransformNormal(Vector3.Forward, rotationMatrix);
                this.up = Vector3.TransformNormal(Vector3.Up, rotationMatrix);

                // Re-normalize orientation vectors
                this.forward.Normalize();
                this.up.Normalize();

                // Re-calculate Right
                this.right = Vector3.Cross(this.forward, this.up);

                // The same instability may cause the 3 orientation vectors may
                // also diverge. Either the Up or Forward vector needs to be
                // re-computed with a cross product to ensure orthagonality
                this.up = Vector3.Cross(this.right, this.forward);

                this.updateDirections = false;

            }
        }
        
        public void MoveTo(Vector3 loc)
        {
            this.location = loc;
        }

        public void Move(Vector3 loc)
        {
            this.location += loc;
        }

        public void Move(GameTime gameTime)
        {
            float dt = (float)gameTime.ElapsedGameTime.TotalSeconds;
            this.Move(this.velocity * dt);
        }

        public void RotateTo(Vector3 rot)
        {

            this.rotation = rot;

            if (this.wrapRotation)
            {

                if (this.rotation.X < this.minRotation.X) 
                { 
                    this.rotation.X = this.maxRotation.X - (this.minRotation.X - this.rotation.X);
                }
                else if (this.rotation.X > this.maxRotation.X)
                {
                    this.rotation.X = this.minRotation.X + (this.rotation.X - this.maxRotation.X);
                }

                if (this.rotation.Y < this.minRotation.Y)
                {
                    this.rotation.Y = this.maxRotation.Y - (this.minRotation.Y - this.rotation.Y);
                }
                else if (this.rotation.Y > this.maxRotation.Y)
                {
                    this.rotation.Y = this.minRotation.Y + (this.rotation.Y - this.maxRotation.Y);
                }

                if (this.rotation.Z < this.minRotation.Z)
                { 
                    this.rotation.Z = this.maxRotation.Z - (this.minRotation.Z - this.rotation.Z);
                }
                else if (this.rotation.Z > this.maxRotation.Z)
                {
                    this.rotation.Z = this.minRotation.Z + (this.rotation.Z - this.maxRotation.Z);
                }
                
            }
            
            this.updateDirections = true;

        }

        public void Rotate(Vector3 rot)
        {
            this.RotateTo(this.rotation + rot);
        }

        public void Rotate(GameTime gameTime)
        {
            float dt = (float)gameTime.ElapsedGameTime.TotalSeconds;
            this.Rotate(this.rotVelocity * dt);
        }
        
        #endregion

    }
}
